package uqbar.comm;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import uqbar.comm.protocol.Output;
import uqbar.comm.protocol.Protocol;
import uqbar.comm.simpleProtocol.SimpleOutput;


public class Caller<Input> {

	public static final String CALLER_TYPE = "PR";
	public static final int ID_SIZE = 5;
	private Connector<Input> connector;

	int nextId = 0;
	Map<Integer, Request<Input>> requests = new HashMap<Integer, Request<Input>>();

	public Caller(final Protocol<Input> protocol) {
		this.connector = Connector.forProtocol(new CallerProtocol<Input>(protocol, this));
	};

	public Connector<Input> getConnector() {
		return this.connector;
	}

	public void open() {
		this.connector.open();
	}

	public void close() {
		this.connector.close();
	}

	// TODO no hace falta sincronizar toooodo el metodo
	public Input call(final Output output) {
		Lock lock = new ReentrantLock();
		Condition condition = lock.newCondition();
		lock.lock();
		try {
			Integer id = this.nextId++;
			Request<Input> request = new Request<Input>(id, output, lock, condition);
			this.requests.put(id, request);
			this.connector.send(this.createOutput(output, id));
			condition.await();
			this.requests.remove(id);
			return request.getResponse();
		} catch (InterruptedException e) {
			throw ProgramException.wrap(e);
		} finally {
			lock.unlock();
		}
	}

	// TODO esto esta horrible
	private Output createOutput(final Output output, final Integer id) {
		try {
			ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
			byteArrayOutputStream.write(StringUtils.fill(id.toString(), Caller.ID_SIZE, '0').getBytes());
			output.send(byteArrayOutputStream);
			return new SimpleOutput(Caller.CALLER_TYPE, new String(byteArrayOutputStream.toByteArray()));
		} catch (IOException e) {
			throw ProgramException.wrap(e);
		}
	}

	public void setResponse(final Integer requestId, final Input response) {
		this.requests.get(requestId).setResponse(response);
	}
}
